package com.hooper.dp.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hooper.dp.dto.*;
import com.hooper.dp.entity.SeckillVoucher;
import com.hooper.dp.entity.VoucherOrder;
import com.hooper.dp.exception.BusinessException;
import com.hooper.dp.mapper.VoucherOrderMapper;
import com.hooper.dp.service.ISeckillVoucherService;
import com.hooper.dp.service.IVoucherOrderService;
import com.hooper.dp.utils.*;
import com.hooper.util.RandomUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.hooper.dp.constant.CommonConstant.*;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author 虎哥
 * @since 2021-12-22
 */
@Service
@Slf4j
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private VoucherOrderMapper voucherOrderMapper;

    @Resource
    private ISeckillVoucherService seckillVoucherService;

    @Resource
    private RabbitTemplate rabbitTemplate;

    @Resource
    private IDWorker idWorker;
    private static final DefaultRedisScript<Long> ADD_MESSAGE_SCRIPT;

    static {
        ADD_MESSAGE_SCRIPT = new DefaultRedisScript<>();
        ADD_MESSAGE_SCRIPT.setLocation(new ClassPathResource("addMessage.lua"));
        ADD_MESSAGE_SCRIPT.setResultType(Long.class);
    }

    private static final String KEY_PREFIX = "hmdp:vouchers:";
    private static final String BOUGHT_USERS = KEY_PREFIX + "boughtUsers:";

    @PostConstruct
    public void initVoucherInfo() {
        //更新开始和结束时间
        List<SeckillVoucher> voucherList = seckillVoucherService.list().stream()
                .peek(item -> {
                            LocalDateTime localDateTime = LocalDateTime.now();
                            item.setBeginTime(localDateTime);
                            item.setEndTime(localDateTime.plusMinutes(30));
                        }
                ).collect(Collectors.toList());
        seckillVoucherService.updateBatchById(voucherList);
        log.info("优惠券开始结束时间更新成功！");
        //将数据预加载到redis中
        List<SeckillVoucher> list = seckillVoucherService.list();
        list.forEach(voucher -> {
            stringRedisTemplate.opsForValue().set(KEY_PREFIX + "stock:" + voucher.getVoucherId(), voucher.getStock() + "");
        });

        List<VoucherDto> dtoList = list.stream().map(item -> {
            VoucherDto voucherDto = new VoucherDto();
            voucherDto.setBeginTime(item.getBeginTime().toEpochSecond(ZoneOffset.UTC));
            voucherDto.setEndTime(item.getEndTime().toEpochSecond(ZoneOffset.UTC));
            voucherDto.setVoucherId(item.getVoucherId());
            return voucherDto;
        }).collect(Collectors.toList());
        dtoList.forEach(voucher -> {
            Map<String, Object> map = BeanUtil.beanToMap(voucher, new HashMap<>(), CopyOptions.create().ignoreNullValue()
                    .setFieldValueEditor((k, v) -> String.valueOf(v)));
            stringRedisTemplate.opsForHash().putAll(KEY_PREFIX + voucher.getVoucherId(), map);
            stringRedisTemplate.expire(KEY_PREFIX + voucher.getVoucherId(), COMMON_TTL, TimeUnit.MINUTES);
        });
        log.info("优惠券信息成功存入Redis中");

    }

    @Override
    public Result seckillVoucher(Long voucherId) {
        String key = KEY_PREFIX + voucherId;
        //获取当前用户id
        UserDTO user = Optional.ofNullable(UserHolder.getUser()).orElseGet(UserDTO::new);
        //模拟多个用户
        Long userId = Optional.ofNullable(user.getId())
                .orElse(RandomUtil.randomInt(10) == 1 ? 1L :
                        RandomUtil.randomLong(true));
        //根据用户id和优惠券的id加锁
        RedisLockUtil lock = new RedisLockUtil(stringRedisTemplate, userId.toString() + voucherId);
        try {
            if (lock.tryLock()) {
                //查询优惠券
                Map<Object, Object> voucherInfo = stringRedisTemplate.opsForHash().entries(key);
                if (CollectionUtil.isEmpty(voucherInfo)) {
                    return Result.fail("无该优惠券信息！");
                }
                long beginTime = Long.parseLong((String) voucherInfo.get("beginTime"));
                long endTime = Long.parseLong((String) voucherInfo.get("endTime"));
                long now = LocalDateTime.now().toEpochSecond(ZoneOffset.UTC);
                //判断优惠券是否到达开始时间
                if (now > endTime || now < beginTime) {
                    return Result.fail("未到开始时间或已经结束！");
                }
                //将当前用添加到已购买的set中
                String alreadyBoughtSet = BOUGHT_USERS + voucherId;
                Long addResult = stringRedisTemplate.opsForSet().add(alreadyBoughtSet, userId.toString());
                //设置过期时间
                stringRedisTemplate.expire(alreadyBoughtSet, (beginTime - endTime) + 1000 * 30, TimeUnit.MILLISECONDS);
                //判断该用户是否已在已购买的set中
                if (new Long(0L).equals(addResult)) {
                    return Result.fail("一人一单！");
                }
                //判断库存是否充足
                Long stock = stringRedisTemplate.opsForValue().decrement(KEY_PREFIX + "stock:" + voucherId);
                if (Optional.ofNullable(stock).orElse(-1L) < 0) {
                    return Result.fail("无库存！");
                }
                //生成全局唯一ID
                Long orderId = idWorker.nextId(ORDER_ID_KEY_PREFIX + voucherId);
                //向消息队列中添加消息
                String queueName = "order.queue";
                OrderMessage message = new OrderMessage();
                message.setOrderId(orderId);
                message.setUserId(userId);
                message.setVoucherId(voucherId);
                //发送消息
                rabbitTemplate.convertAndSend(queueName, message);
                return Result.ok("订单id" + orderId);
            }
        } finally {
            lock.unlock();
        }
        //若未获得锁，则表明该用户已获得锁，则为重复购买的情况
        return Result.fail("一个用户一单！");
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void createOrder(OrderMessage orderMessage) {
        Long userId = orderMessage.getUserId();
        Long voucherId = orderMessage.getVoucherId();
        Long orderId = orderMessage.getOrderId();
        LambdaUpdateWrapper<SeckillVoucher> wrapper = new LambdaUpdateWrapper<>();
        wrapper.setSql("stock = stock - 1")
                .eq(SeckillVoucher::getVoucherId, voucherId);
        boolean success = seckillVoucherService.update(wrapper);
        if (!success) {
            throw new BusinessException("无库存!");
        }
        VoucherOrder voucherOrder = new VoucherOrder();
        voucherOrder.setId(orderId);
        voucherOrder.setVoucherId(voucherId);
        voucherOrder.setUserId(userId);
        voucherOrderMapper.insert(voucherOrder);
    }


}
